﻿namespace Cellfish.Toolbox.Facts.Tasks
{
    using System;
    using System.Threading;
    using System.Threading.Tasks;

    using Cellfish.Test;
    using Cellfish.Toolbox.Tasks;

    using Microsoft.VisualStudio.TestTools.UnitTesting;

    [TestClass]
    public class WithTimeoutExtensionsFacts
    {
        [TestMethod]
        public void Given_a_null_task_When_adding_timeout_Then_ArgumentNullException_is_thrown()
        {
            Task task1 = null;
            Task<int> task2 = null;
            Ensure.Throws<ArgumentNullException>(() => task1.WithTimeout(TimeSpan.Zero), "Failed for Task");
            Ensure.Throws<ArgumentNullException>(() => task2.WithTimeout(TimeSpan.Zero), "Failed for Task<T>");
        }

        [TestMethod]
        public void Given_a_task_When_adding_negative_timeout_Then_ArgumentOutOfRange_is_thrown()
        {
            Task task1 = Task.Delay(TimeSpan.FromSeconds(5));
            Task<int> task2 = Task.Factory.StartNew(
                () =>
                    {
                        Task.Delay(TimeSpan.FromSeconds(5)).Wait();
                        return 42;
                    });
            Ensure.Throws<ArgumentOutOfRangeException>(() => task1.WithTimeout(TimeSpan.FromMilliseconds(-1)), "Failed for Task");
            Ensure.Throws<ArgumentOutOfRangeException>(() => task2.WithTimeout(TimeSpan.FromMilliseconds(-1)), "Failed for Task<T>");
        }

        [TestMethod]
        public void Given_a_completed_task_When_adding_timeout_Then_result_is_returned_immediatly()
        {
            var task = Task.FromResult(42);
            Assert.AreEqual(42, task.WithTimeout(TimeSpan.Zero).Result);
        }

        [TestMethod]
        public void Given_a_task_that_completes_before_timeout_When_adding_timeout_Then_result_is_returned()
        {
            var continueTask = new ManualResetEvent(false);
            var task = Task.Factory.StartNew(
                () =>
                    {
                        continueTask.WaitOne();
                        return 42;
                    });
            var taskWithTimeout = task.WithTimeout(TimeSpan.FromSeconds(5));
            continueTask.Set();
            Assert.AreEqual(42, taskWithTimeout.Result);
        }

        [TestMethod]
        public void Given_a_task_that_completes_after_timeout_When_adding_timeout_Then_TimeoutException_is_thrown()
        {
            var task = Task.Delay(TimeSpan.FromSeconds(5));
            Ensure.TaskFails<TimeoutException>(task.WithTimeout(TimeSpan.Zero));    
        }

        private static Task<T> StartedTask<T>(Func<T> taskBody)
        {
            var started = new ManualResetEvent(false);
            var continueTask = new ManualResetEvent(false);
            var task = Task.Factory.StartNew(
                () =>
                    {
                        started.Set();
                        continueTask.WaitOne();
                        return taskBody();
                    });
            started.WaitOne();
            continueTask.Set();
            return task;
        }
    }
}
